/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package ca.inrs.ete.app.shopx.authenticator;

import java.lang.ref.WeakReference;

import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountManager;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ResultReceiver;
import android.text.TextUtils;
import android.util.Log;
import ca.inrs.ete.app.shopx.R;
import ca.inrs.ete.app.shopx.util.Constants;
import ca.inrs.ete.app.shopx.util.NetworkUtilities;

/**
 * Activity which displays login screen to the user.
 */
public class AuthenticatorActivity extends AccountAuthenticatorActivity {
	private static final String TAG = "SHOPLog.AuthentActivity";

	/** The Intent extras */
	public static final String PARAM_USERNAME       = "username";
	public static final String PARAM_AUTHTOKEN_TYPE = "authtokenType";

	private AccountManager mAccountManager;

	/** Keep track of the login task so can cancel it if requested */
	private UserLoginTask mAuthTask = null;

	/**
	 * If set we are just checking that the user knows their credentials; this
	 * doesn't cause the user's password or authToken to be changed on the
	 * device.
	 */
	private Boolean mConfirmCredentials= false;
	private boolean mRequestNewAccount = false;

    /** For posting authentication attempts back to UI thread */
    private static class ActionHandler extends Handler {
    	private static final int SHOW_PROGRESS = 1;
    	private static final int HIDE_PROGRESS = 2;
    	private static final int SHOW_MESSAGE  = 3;
        private final WeakReference<AuthenticatorActivity> mActivity;

        ActionHandler(AuthenticatorActivity activity) {
        	mActivity =  new WeakReference<AuthenticatorActivity>(activity);
        }
        
        @Override
        public void handleMessage(Message msg) {
        	AuthenticatorActivity activity = mActivity.get();

            switch (msg.what) {
            case SHOW_PROGRESS: activity.showProgress(); break;
            case HIDE_PROGRESS: activity.hideProgress(); break;
            case SHOW_MESSAGE:  activity.showMessage((CharSequence) msg.obj); break;
            }
        }
    };
    private Handler mHandler = new ActionHandler(this);
    
	private AlertDialog msgBox   = null;

	private String  mUrl;
	private String  mUsername;
	private String  mPassword;
	private String  mPollFreqS;
	private int     mPollFreqI;
	private String  mLanguage;
	private String  mWifionly;

	private ResultReceiver doActionCB = null;

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void onCreate(Bundle icicle) {
		super.onCreate(icicle);

		Log.i(TAG, "onCreate(" + icicle + ")");

		mAccountManager = AccountManager.get(this);

		Log.i(TAG, "loading data from Intent");
		final Intent intent = getIntent();
		mUsername  = intent.getStringExtra(PARAM_USERNAME);

		mRequestNewAccount = mUsername == null;
		Log.i(TAG, "    request new: " + mRequestNewAccount);

		// ---  Build intent with 2 callbacks
		Intent settingsIntent = new Intent(this, AuthenticationSettings.class);

		// ---  Add the 2 callback
		settingsIntent.putExtra(AuthenticationSettings.AUTHEN_SETTINGS_BUTTON_CB, new ResultReceiver(null) {
			@Override
			protected void onReceiveResult(int resultCode, Bundle resultData) {
				AuthenticatorActivity.this.handleLogin(resultData);
			}
		});
		settingsIntent.putExtra(AuthenticationSettings.AUTHEN_SETTINGS_REGISTER_CB, new ResultReceiver(null) {
			@Override
			protected void onReceiveResult(int resultCode, Bundle resultData) {
				AuthenticatorActivity.this.doActionCB = resultData.getParcelable(AuthenticationSettings.AUTHEN_SETTINGS_ACTION_CB);
			}
		});

		/**
		 * Add account information from the account manager.
		 * If the account does not exist, the default values will be
		 * taken from the xml preference file.
		 */
		if (! mRequestNewAccount) {
			Account[] accounts = mAccountManager.getAccountsByType(Constants.ACCOUNT_TYPE);
			assert(accounts.length == 1);
			Account account = accounts[0];
			
			settingsIntent.putExtra(AuthenticationSettings.AUTHEN_PREF_URI, 
					                mAccountManager.getUserData(account, Constants.ACCOUNT_KEY_URI));
			settingsIntent.putExtra(AuthenticationSettings.AUTHEN_PREF_USR, mUsername);
			settingsIntent.putExtra(AuthenticationSettings.AUTHEN_PREF_PWD,
									mAccountManager.getPassword(account));
			settingsIntent.putExtra(AuthenticationSettings.AUTHEN_PREF_FRQ,
									mAccountManager.getUserData(account, Constants.ACCOUNT_KEY_FREQUENCY));
			settingsIntent.putExtra(AuthenticationSettings.AUTHEN_PREF_LNG,
									mAccountManager.getUserData(account, Constants.ACCOUNT_KEY_LANGUAGE));
			settingsIntent.putExtra(AuthenticationSettings.AUTHEN_PREF_WFI,
									mAccountManager.getUserData(account, Constants.ACCOUNT_KEY_WIFIONLY));
		}
		
		// ---  Start activity
		startActivity(settingsIntent);

		/*		
		if (!TextUtils.isEmpty(mUsername)) mUsernameEdit.setText(mUsername);
		mMessage.setText(getMessage());
		 */
	}

	/**
	 * Handles onClick event on the Submit button. Sends username/password to
	 * the server for authentication. The button is configured to call
	 * handleLogin() in the layout XML.
	 *
	 * @param view The Submit button for which this method is invoked
	 */
	private void handleLogin(Bundle bundle) {
		if (mRequestNewAccount) {
			mUsername = bundle.getString(AuthenticationSettings.AUTHEN_PREF_USR, "");
		}

		mUrl      = bundle.getString(AuthenticationSettings.AUTHEN_PREF_URI, "");
		mUsername = bundle.getString(AuthenticationSettings.AUTHEN_PREF_USR, "");
		mPassword = bundle.getString(AuthenticationSettings.AUTHEN_PREF_PWD, "");
		mPollFreqS= bundle.getString(AuthenticationSettings.AUTHEN_PREF_FRQ, "");
		mLanguage = bundle.getString(AuthenticationSettings.AUTHEN_PREF_LNG, "");
		mWifionly = bundle.getString(AuthenticationSettings.AUTHEN_PREF_WFI, "");

		mPollFreqI = Integer.parseInt(mPollFreqS);

		if (TextUtils.isEmpty(mUrl) || 
				TextUtils.isEmpty(mUsername) || 
				TextUtils.isEmpty(mPassword) ||
				TextUtils.isEmpty(mPollFreqS)||
				TextUtils.isEmpty(mLanguage) ||
				TextUtils.isEmpty(mWifionly)) {
			Message msg = Message.obtain(mHandler, ActionHandler.SHOW_MESSAGE);
			msg.obj = getMessage();
			mHandler.sendMessage(msg);
		} else {
			// Show a progress dialog, and kick off a background task to perform
			// the user login attempt.
			mHandler.sendMessage( Message.obtain(mHandler, ActionHandler.SHOW_PROGRESS) );
			mAuthTask = new UserLoginTask();
			mAuthTask.execute();
		}
	}

	/**
	 * Called from the thread when response is received from the server
	 * for confirm credentials request. See onAuthenticationResult().
	 * Sets the AccountAuthenticatorResult which is sent back to the caller.
	 *
	 * @param result the confirmCredentials result.
	 */
	private void finishConfirmCredentials(boolean result) {
		Log.i(TAG, "finishConfirmCredentials()");

		final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE);
		mAccountManager.setPassword(account, mPassword);

		final Intent intent = new Intent();
		intent.putExtra(AccountManager.KEY_BOOLEAN_RESULT, result);
		setAccountAuthenticatorResult(intent.getExtras());
		setResult(RESULT_OK, intent);
		finish();
	}

	/**
	 * Called from the thread when response is received from the server
	 * for authentication request. See onAuthenticationResult(). Sets the
	 * AccountAuthenticatorResult which is sent back to the caller. We store the
	 * authToken that's returned from the server as the 'password' for this
	 * account - so we're never storing the user's actual password locally.
	 *
	 * @param result the confirmCredentials result.
	 */
	private void finishLogin(String authToken) {
		Log.i(TAG, "finishLogin()");

		final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE);
		if (mRequestNewAccount) {
			mAccountManager.addAccountExplicitly(account, mPassword, null);
			mAccountManager.setUserData(account, Constants.ACCOUNT_KEY_URI, mUrl);
			mAccountManager.setUserData(account, Constants.ACCOUNT_KEY_FREQUENCY, mPollFreqS);
			mAccountManager.setUserData(account, Constants.ACCOUNT_KEY_LANGUAGE, mLanguage);
			mAccountManager.setUserData(account, Constants.ACCOUNT_KEY_WIFIONLY, mWifionly);

			Bundle extras = new Bundle();
			ContentResolver.addPeriodicSync(account, Constants.CONTENT_AUTHORITY, extras, mPollFreqI*60);
			ContentResolver.setIsSyncable(account, Constants.CONTENT_AUTHORITY, 1);

		} else {
			mAccountManager.setPassword(account, mPassword);
		}

		doActionCB.send(AuthenticationSettings.AUTHEN_ACTION_TERMINATE, null);

		final Intent intent = new Intent();
		intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername);
		intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
		setAccountAuthenticatorResult(intent.getExtras());
		setResult(RESULT_OK, intent);
		finish();
	}

	/**
	 * Called from the thread when the authentication process
	 * completes (see attemptLogin()).
	 *
	 * @param authToken the authentication token returned by the server, or NULL if
	 *            authentication failed.
	 */
	public void onAuthenticationResult(String authToken) {

		boolean success = ((authToken != null) && (authToken.length() > 0));
		Log.i(TAG, "onAuthenticationResult(" + success + ")");

		// Our task is complete, so clear it out
		mAuthTask = null;

		// Hide the progress dialog
		mHandler.sendMessage( Message.obtain(mHandler, ActionHandler.HIDE_PROGRESS) );

		if (success) {
			if (!mConfirmCredentials) {
				finishLogin(authToken);
			} else {
				finishConfirmCredentials(success);
			}
		} else {
			Log.e(TAG, "onAuthenticationResult: failed to authenticate");
			if (mRequestNewAccount) {
				// "Please enter a valid username/password.
				Message msg = Message.obtain(mHandler, ActionHandler.SHOW_MESSAGE);
				msg.obj = getText(R.string.login_activity_loginfail_text_both);
				mHandler.sendMessage(msg);
			} else {
				// "Please enter a valid password." (Used when the
				// account is already in the database but the password
				// doesn't work.)
				Message msg = Message.obtain(mHandler, ActionHandler.SHOW_MESSAGE);
				msg.obj = getText(R.string.login_activity_loginfail_text_pwonly);
				mHandler.sendMessage(msg);
			}
		}
	}

	/**
	 * Called from the thread on cancellation
	 */
	public void onAuthenticationCancel() {
		Log.i(TAG, "onAuthenticationCancel()");

		// Our task is complete, so clear it out
		mAuthTask = null;

		// Hide the progress dialog
		mHandler.sendMessage( Message.obtain(mHandler, ActionHandler.HIDE_PROGRESS) );
	}

	/**
	 * Returns the message to be displayed at the top of the login dialog box.
	 */
	private CharSequence getMessage() {
		if (TextUtils.isEmpty(mUrl)) {
			final CharSequence msg = getText(R.string.login_activity_newaccount_text);
			return msg;
		}
		if (TextUtils.isEmpty(mUsername)) {
			final CharSequence msg = getText(R.string.login_activity_newaccount_text);
			return msg;
		}
		if (TextUtils.isEmpty(mPassword)) {
			return getText(R.string.login_activity_loginfail_text_pwmissing);
		}
		return "(@ @) missing message ";
	}

	/**
	 * Shows the progress UI for a lengthy operation.
	 */
	private void showProgress() {
		if (doActionCB != null)
			doActionCB.send(AuthenticationSettings.AUTHEN_ACTION_SHOW_PROGRESS, null);
	}

	/**
	 * Hides the progress UI for a lengthy operation.
	 */
	private void hideProgress() {
		if (doActionCB != null)
			doActionCB.send(AuthenticationSettings.AUTHEN_ACTION_HIDE_PROGRESS, null);
	}

	private void showMessage(CharSequence msg) {
		if (msgBox == null) {
			final AlertDialog.Builder builder = new AlertDialog.Builder(this);
			builder.setTitle(R.string.title_activity_login)
			.setCancelable(false)
			.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int id) {
				}
			});
			msgBox = builder.create();
		}

		msgBox.setMessage(msg);
		msgBox.show();
	}

	/**
	 * Represents an asynchronous task used to authenticate a user against the
	 * SampleSync Service
	 */
	public class UserLoginTask extends AsyncTask<Void, Void, String> {

		@Override
		protected String doInBackground(Void... params) {
			// We do the actual work of authenticating the user
			// in the NetworkUtilities class.
			try {
				return NetworkUtilities.authenticate(mUrl, mUsername, mPassword);
			} catch (Exception ex) {
				Log.e(TAG, "UserLoginTask.doInBackground: failed to authenticate");
				Log.i(TAG, ex.toString());
				return null;
			}
		}

		@Override
		protected void onPostExecute(final String authToken) {
			// On a successful authentication, call back into the Activity to
			// communicate the authToken (or null for an error).
			onAuthenticationResult(authToken);
		}

		@Override
		protected void onCancelled() {
			// If the action was canceled (by the user clicking the cancel
			// button in the progress dialog), then call back into the
			// activity to let it know.
			onAuthenticationCancel();
		}
	}

}
